class Array:
    def __init__(self, size=32, init=None):
        self._size = size
        self._items = [init] * size

    # a[index]
    def __getitem__(self, index):
        return self._items[index]

    # a[index] = xxx
    def __setitem__(self, index, value):
        self._items[index] = value

    def __len__(self):
        return self._size

    def clear(self, value=None):
        for i in range(len(self._items)):
            self._items[i] = value

    def __iter__(self):
        for item in self._items:
            yield item


class Slot:
    # hash表中的数组的槽
    '''
    1、从未使用HashMap.UNUSED。此槽没有被使用过或冲突过，查找只有找到UNUSED就不再查找了
    2、使用过但是remove了，此时是HashMap.EMPTY，该谈差点后边的元素仍可能是有key
    3、使用中slot
    '''

    def __init__(self, key, value):
        self.key, self.value = key, value


class HashTable:  # hash表
    UNUSED = None  # 槽未被使用
    EMPTY = Slot(None, None)  # 槽被使用后移除

    def __init__(self):
        self._table = Array(8, init=HashTable.UNUSED)  # [None]* 32
        self.length = 0

    @property
    def _load_factor(self):  # 负载因子
        return self.length / float(len(self._table))  # 哈希表实际的长度/设计长度

    def __len__(self):
        return self.length

    def _hash(self, key):  # 哈希取模
        return abs(hash(key)) % len(self._table)

    def _find_key(self, key):  # 寻找key的位置
        index = self._hash(key)
        _len = len(self._table)
        while self._table[index] is not HashTable.UNUSED:
            if self._table[index] is HashTable.EMPTY:
                index = (index * 5 + 1) % _len  # cpython使用的一种解决冲突的方式
                continue  # 跳过该次循环
            elif self._table[index].key == key:  # 直到匹配为止，否则为None
                return index
            else:
                index = (index * 5 + 1) % _len
        return None

    def _find_slot_for_insert(self, key):  # 寻找可插入的空槽的位置
        index = self._hash(key)
        _len = len(self._table)
        while not self._slot_can_insert(index):  # 如果槽不可以插入，重新计算哈希值
            index = (index * 5 + 1) % _len
        return index

    def _slot_can_insert(self, index):  # 判断slot槽是否可以插入
        return (self._table[index] is HashTable.EMPTY or self._table[index] is HashTable.UNUSED)

    def __contains__(self, key):  # 魔术方法,key in self进行判断
        index = self._find_key(key)
        return index is not None

    def add(self, key, value):  # 插入key-value值
        if key in self:  # 更新操作
            index = self._find_key(key)
            self._table[index].value = value
            return False
        else:  # 插入操作
            index = self._find_slot_for_insert(key)
            self._table[index] = Slot(key, value)
            self.length += 1
            if self._load_factor >= 0.8:
                self._rehash()
            return True

    def _rehash(self):
        old_table = self._table
        newsize = len(self._table) * 2
        self._table = Array(newsize, HashTable.UNUSED)

        self.length = 0

        for slot in old_table:
            if slot is not HashTable.UNUSED and slot is not HashTable.EMPTY:
                index = self._find_slot_for_insert(slot.key)
                self._table[index] = slot
                self.length += 1

    def get(self, key, default=None):
        index = self._find_key(key)
        if index is None:
            return default
        else:
            return self._table[index].value

    def remove(self, key):
        index = self._find_key(key)
        if index is None:
            return KeyError()
        value = self._table[index].value
        self.length -= 1
        self._table[index] = HashTable.EMPTY
        return value

    def __iter__(self):
        for slot in self._table:
            if slot not in (HashTable.EMPTY, HashTable.UNUSED):
                yield slot.key


def test_hash_table():
    h = HashTable()
    h.add('a', 0)
    h.add('b', 1)
    h.add('c', 2)

    assert len(h) == 3
    assert h.get('a') == 0
    assert h.get('b') == 1
    assert h.get('c') == 2

    assert h.get('ad') is None
    h.remove('a')
    assert h.get('a') is None
    assert sorted(list(h)) == ['b', 'c']

    n = 50
    for i in range(n):
        h.add(i, i)
    for i in range(n):
        assert h.get(i) == i


if __name__ == '__main__':
    print(dir(HashTable()))
